﻿using System.Threading;
using System.Threading.Tasks;

using iTool.Cloud.DataSearch.DirectoryProvider;
using iTool.ClusterComponent;
using iTool.SQL.AIHandle;

using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
using Lucene.Net.Spatial.Prefix.Tree;
using Lucene.Net.Spatial.Prefix;
using Lucene.Net.Spatial;
using Lucene.Net.Store;
using Lucene.Net.Util;

using Spatial4n.Context;
using System;
using Lucene.Net.Spatial.Queries;
using Spatial4n.Distance;
using Spatial4n.Shapes;
using System.Linq;
using Lucene.Net.Queries.Function;
using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom;
using J2N.Collections.Generic;
using Newtonsoft.Json;

namespace iTool.Cloud.DataSearch.ServiceProvider
{

    public class IndexReaderService : iToolServiceBase, IIndexReaderService
    {
        IndexSearcher searcher;
        JieBaAnalyzer analyzer;
        IndexReader reader;
        string tableName;

        public async override Task OnActivateAsync()
        {
            this.tableName = this.GetStringKey();

            var directory = new iToolCloudDirectory(this.tableName, true);
            this.analyzer = new JieBaAnalyzer(TokenizerMode.Search);
            this.reader = DirectoryReader.Open(directory);
            this.searcher = new IndexSearcher(reader);

            await base.OnActivateAsync();
        }


        private Task<SearchResponse> QueryAsync(SearchIndexOptions searchIndex, Query query, Sort? sort = null) 
        {
            // SearcherManager
            //ReferenceManager<IndexSearcher> searcherManager = new SearcherManager(writer, true, new SearcherFactory());
            //searcherManager.MaybeRefreshBlocking();
            //IndexSearcher searcher = searcherManager.Acquire();
            ////after using，释放掉
            //searcherManager.Release(searcher);
            if (sort == null)
            {
                sort = Sort.RELEVANCE; 
            }

            IndexReader newReader = DirectoryReader.OpenIfChanged((DirectoryReader)reader);
            if (newReader != null)
            {
                this.searcher = new IndexSearcher(newReader);
                reader.Dispose();
                this.reader = newReader;
            }

            System.Console.WriteLine("this.reader.NumDocs:"+ this.reader.NumDocs);
            if (this.reader.NumDocs == 0)
            {
                return Task.FromResult(new SearchResponse());
            }

            // 分页
            // searcher.SearchAfter(new ScoreDoc(1,1,1), query, 20);
            TopDocs tds = null;
            bool isNeedToken = false;
            switch (searchIndex.IndexType)
            {
                case IndexTypeOptions.OnlySize:
                    isNeedToken = true;
                    tds = searcher.Search(query, searchIndex.Size, sort);
                    break;
                case IndexTypeOptions.PageAndSize:
                    {
                        FieldDoc? fieldDoc = new SearchScoreDocItemOptions(searchIndex.PreviouToken).GetFieldDocItem();
                        for (int i = 1; i <= searchIndex.Page; i++)
                        {
                            tds = searcher.SearchAfter(fieldDoc, query, searchIndex.Size, sort);
                            if (tds?.TotalHits > 0)
                            {
                                fieldDoc = (FieldDoc)tds.ScoreDocs.Last();
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    break;
                case IndexTypeOptions.OnlyToken:
                    {
                        isNeedToken = true;
                        FieldDoc? scoreDoc = new SearchScoreDocItemOptions(searchIndex.PreviouToken).GetFieldDocItem();
                        tds = searcher.SearchAfter(scoreDoc, query, this.reader.NumDocs > 30 ? 30 : this.reader.NumDocs, sort);
                    }
                    break;
                case IndexTypeOptions.TokenAndSize:
                    {
                        isNeedToken = true;
                        FieldDoc? scoreDoc = new SearchScoreDocItemOptions(searchIndex.PreviouToken).GetFieldDocItem();
                        tds = searcher.SearchAfter(scoreDoc, query, searchIndex.Size, sort);
                    }
                    break;
                case IndexTypeOptions.None:
                    tds = searcher.Search(query, this.reader.NumDocs > 10000 ? 10000 : this.reader.NumDocs, sort);
                    break;
            }

            System.Console.WriteLine("tds.TotalHits:" + tds?.TotalHits);
            long[] result = new long[tds.ScoreDocs.Length];
            for (int i = 0; i < tds.ScoreDocs.Length; i++)
            {
                var doc = searcher.Doc(tds.ScoreDocs[i].Doc);
                long.TryParse(doc.Get("id"), out result[i]);
            }
            return Task.FromResult(new SearchResponse
            {
                TotalHits = tds.TotalHits,
                Keys = result,
                LastScoreDocItem = isNeedToken ? (tds.TotalHits > 0 ? new SearchScoreDocItemOptions((FieldDoc)tds.ScoreDocs.Last()) : null) : null
            });
        }

        public Task<SearchResponse> MultipleConditionsAsync(SearchIndexOptions searchIndex, SortByOptions? SortByOptions, params ChildIndexFn[] searchs)
        {
            BooleanQuery booleanQuery = new BooleanQuery();
            foreach (var search in searchs)
            {
                switch (search.IndexMode)
                {
                    case IndexModeOptions.Fields:
                    case IndexModeOptions.Shoube:
                    case IndexModeOptions.Must:
                    case IndexModeOptions.MustNot:
                        {
                            if (search.Values[^1].IndexOf('%') > -1)
                                search.Values[^1] = search.Values[^1].Replace('%','*');

                            MultiFieldQueryParser queryParser = new MultiFieldQueryParser(LuceneVersion.LUCENE_48, search.Fields.ToArray(), analyzer);
                            var query = queryParser.Parse(search.Values[^1]);
                            booleanQuery.Add(query, search.IndexMode == IndexModeOptions.Must ? Occur.MUST
                                : (search.IndexMode == IndexModeOptions.MustNot ? Occur.MUST_NOT
                                : Occur.SHOULD));
                        }
                        break;

                    case IndexModeOptions.MustDistMap:
                    case IndexModeOptions.MustNotDistMap:
                    case IndexModeOptions.ShoubeDistMap:
                        {
                            if (search.Values.Count < 3)
                            {
                                throw new Exception("DistMap parameter exception");
                            }
                            double.TryParse(search.Values[0], out double x);
                            double.TryParse(search.Values[1], out double y);
                            double.TryParse(search.Values[^1], out double inDistance);

                            if (x == 0.0 || y == 0.0)
                            {
                                throw new Exception("DistMap(x,y) parameter exception");
                            }

                            if (inDistance == 0.0)
                            {
                                throw new Exception("inDistance parameter exception");
                            }

                            SpatialStrategy strategy = new RecursivePrefixTreeStrategy(new GeohashPrefixTree(SpatialContext.Geo, 11), string.Intern(string.Join('_', search.Fields)));

                            IPoint iPoint = SpatialContext.Geo.MakePoint(x, y);
                            // 搜索方圆千米范围以内 {inDistance} km,其中半径为 {inDistance} KM
                            double distance = DistanceUtils.Dist2Degrees(inDistance, DistanceUtils.EarthMeanRadiusKilometers);
                            IShape shape = SpatialContext.Geo.MakeCircle(iPoint, distance);
                            SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);

                            Query query = strategy.MakeQuery(args);

                            booleanQuery.Add(query, 
                                search.IndexMode == IndexModeOptions.MustDistMap ? Occur.MUST 
                                : (search.IndexMode == IndexModeOptions.MustNotDistMap ? Occur.MUST_NOT
                                : Occur.SHOULD));
                        }
                        break;
                }
                
            }

            Sort? sort = null;
            // sort
            if (SortByOptions != null)
            {
                if (SortByOptions.IsSortDist)
                {
                    if (SortByOptions.Values.Count != 2)
                    {
                        throw new Exception("DistMap parameter exception");
                    }

                    double.TryParse(SortByOptions.Values[0], out double x);
                    double.TryParse(SortByOptions.Values[1], out double y);

                    SpatialStrategy strategy = new RecursivePrefixTreeStrategy(new GeohashPrefixTree(SpatialContext.Geo, 11), string.Intern(string.Join('_', SortByOptions.Fields)));
                    IPoint iPoint = SpatialContext.Geo.MakePoint(x, y);
                    // 计算命中坐标距离圆点的距离
                    ValueSource valueSource = strategy.MakeDistanceValueSource(iPoint, DistanceUtils.DegreesToKilometers);

                    // 距离远近降序排； false表示降序,true表示升序
                    var field = valueSource.GetSortField(SortByOptions.SortOrder == SqlSortOrder.Ascending);
                    sort = new Sort(field).Rewrite(searcher);
                }
                else
                {
                    List<SortField> sortFields = new List<SortField>();

                    foreach (var item in SortByOptions.Fields)
                    {
                        sortFields.Add(new SortField(item, SortFieldType.DOUBLE, SortByOptions.SortOrder == SqlSortOrder.Ascending));
                    }
                    sort = new Sort();
                    sort.SetSort(sortFields.ToArray());
                }
            }

            // 分页
            
            return this.QueryAsync(searchIndex, booleanQuery, sort);
        }

        public Task<SearchResponse> ContainsAsync(SearchIndexOptions searchIndex, string keyword, params string[] fields)
        {
            MultiFieldQueryParser queryParser = new MultiFieldQueryParser(LuceneVersion.LUCENE_48, fields, analyzer);
            var query = queryParser.Parse(keyword);
            return this.QueryAsync(searchIndex,query);
        }

        public Task<SearchResponse> LeftContainsAsync(SearchIndexOptions searchIndex, string keyword, params string[] fields)
        {
            BooleanQuery booleanQuery = new BooleanQuery();
            foreach (var item in fields)
            {
                booleanQuery.Add(new PrefixQuery(new Term(item, keyword)), Occur.SHOULD);
            }
            return this.QueryAsync(searchIndex, booleanQuery);
        }

        public Task<SearchResponse> RightContainsAsync(SearchIndexOptions searchIndex, string keyword, params string[] fields)
        {
            BooleanQuery booleanQuery = new BooleanQuery();
            foreach (var item in fields)
            {
                booleanQuery.Add(new WildcardQuery(new Term(item, "*" + keyword)), Occur.SHOULD);
            }
            return this.QueryAsync(searchIndex, booleanQuery);
        }


        void test()
        {

            //1. “:” 指定字段查指定值，如返回所有值 *:*
            //2. “?” 表示单个任意字符的通配
            //3. “*” 表示多个任意字符的通配（不能在检索的项开始使用* 或者?符号）
            //4. “~” 表示模糊检索，如检索拼写类似于”roam”的项这样写：roam~将找到形如foam和roams的单词；roam~0.8，检索返回相似度在0.8以上的记录。
            //5.邻近检索，如检索相隔10个单词的”apache”和”jakarta”，”jakarta apache”~10
            //6. “^” 控制相关度检索，如检索jakarta apache，同时希望去让”jakarta”的相关度更加好，那么在其后加上”^”符号和增量值，即jakarta ^ 4 apache
            //7.布尔操作符AND、||
            //8.布尔操作符OR、&&

            //9.布尔操作符NOT、!、- （排除操作符不能单独与项使用构成查询）
            //10. “+” 存在操作符，要求符号”+”后的项必须在文档相应的域中存在
            //11. () 用于构成子查询
            //12. [] 包含范围检索，如检索某时间段记录，包含头尾，date: [200707 TO 200710]
            //13. { }
            //            不包含范围检索，如检索某时间段记录，不包含头尾
            //date:{ 200707 TO 200710}
            //            14. \ 转义操作符，特殊字符包括 + - && || !( ) { }
            //            [ ] ^ ” ~* ? : \

            // [包含查询]
            new TermQuery(new Term("content", "lucene"));

            // [多条件]
            TermQuery termQuery1 = new TermQuery(new Term("content", "java"));
            TermQuery termQuery2 = new TermQuery(new Term("content", "perl"));
            BooleanQuery booleanQuery = new BooleanQuery();
            //Occur.SHOULD 应该包含
            //Occur.MUST 必须包含
            //Occur.MUST_NOT 必须不包含
            booleanQuery.Add(termQuery1, Occur.SHOULD);
            booleanQuery.Add(termQuery2, Occur.SHOULD);

            // [通配符查询] 如果你想对某单词进行通配符查询，你可以用WildcardQuery，通配符包括’?’匹配一个任意字符和’*’匹配零个或多个任意字符，例如你搜索’use*’，你可能找到’useful’或者’useless’：
            new WildcardQuery(new Term("content", "use*"));

            //[跨词查询] 你可能对中日关系比较感兴趣，想查找‘中’和‘日’挨得比较近（5个字的距离内）的文章，超过这个距离的不予考虑，你可以：
            PhraseQuery query = new PhraseQuery();
            query.Slop = 5;
            query.Add(new Term("content ", "中"));
            query.Add(new Term("content", "日"));
            //那么它可能搜到“中日合作……”、“中方和日方……”，但是搜不到“中国某高层领导说日本欠扁”。

            //[以什么开头] 如果你想搜以‘中’开头的词语，你可以用PrefixQuery：
            new PrefixQuery(new Term("content ", "中"));
            // 中国...，中西...

            //[相似词 搜索] FuzzyQuery用来搜索相似的term，使用Levenshtein算法。假设你想搜索跟‘wuzza’相似的词语，你可以：
            new FuzzyQuery(new Term("content", "wuzza"));
            //你可能得到‘fuzzy’和‘wuzzy’。

            //[范围查询] 另一个常用的Query是RangeQuery，你也许想搜索时间域从20060101到20060130之间的document，你可以用RangeQuery：
            new TermRangeQuery("time", new BytesRef("20060101"), new BytesRef("20060130"), true, true);
            
        }

        public void OnActivateAsync(long hash, string name)
        {
            this.tableName = name;
            var directory = new iToolCloudDirectory(this.tableName, true);
            this.analyzer = new JieBaAnalyzer(TokenizerMode.Search);
            this.reader = DirectoryReader.Open(directory);
            this.searcher = new IndexSearcher(reader);
        }

        public Task FlushAsync()
        {
            return Task.CompletedTask;
        }

        
    }
}
